'''
Created on 09.08.2011

@author: osman
'''

from opf.exceptions import OPFError
from opf.context import addArc, noChecks
from opf.constants import ANY, UNIT

class AtomicPredicateSubjectTypeError(OPFError): pass
class AtomicPredicateObjectTypeError(OPFError): pass
class AtomicPredicateArgCountError(OPFError): pass
class WrongRoleName(OPFError): pass
class WrongNumberOfArguments(OPFError): pass

class PredicateBase:
    @property
    def roleNames(self): return self._roleNames
    def _checkRoleNames(self, names):
        for arg in names:
            if arg not in self._roleNames:
                raise WrongRoleName(roleName = arg,
                                    possibleRoleNames = set(self._roleNames))
    

class AtomicPredicate(PredicateBase):
    def __init__(self, check, cardStart, cardStop):
        c = check.__code__
        argNames = c.co_varnames[:c.co_argcount]
        annotations = check.__annotations__
        self.__name__ = annotations.get('return', check.__name__)
        nargs = c.co_argcount
        if nargs > 2:
            raise AtomicPredicateArgCountError(predicate = self.__name__,
                                                    argCount = c.co_argcount)
        self.__subjType = self.__objType = None 
        if nargs > 0:
            self.__subjType = annotations.get(argNames[0])
        if nargs > 1:
            self.__objType = nargs > 1 and annotations.get(argNames[1])
        self.__cardStart = cardStart
        self.__cardStop = cardStop
        self._roleNames = argNames
        self._roleCount = nargs
        self.__check = check
        self.__opf_ontology__ = None
        self.__opf_kind__ = 'predicate'
    def __repr__(self): return self.__name__
    def __call__(self, *args, **kwargs):
        nroles = self._roleCount
        subj = obj = UNIT
        argsToCheck = ()
        nargs = max(len(args), len(kwargs))
        if nargs != nroles: raise WrongNumberOfArguments(actualNumber = nargs,
                                                         mustBe = nroles)
        if nroles > 0:
            if args: subj = args[0]
            else: subj = kwargs[self._roleNames[0]]
            argsToCheck = (subj,)
        if nroles > 1:
            if args: obj = args[1]
            else: obj = kwargs[self._roleNames[1]]
            argsToCheck = (subj, obj)
        prohibitChecks = noChecks()
        checkResult = True
        if not prohibitChecks:
            # TODO: add cardinality check?
            if (self.__subjType is not None and not isinstance(subj, self.__subjType)):
                raise AtomicPredicateSubjectTypeError(predicate = self, 
                    actualType = type(subj), typeMustBe = self.__subjType)
            if (self.__objType is not None and not isinstance(obj, self.__objType)):
                raise AtomicPredicateObjectTypeError(predicate = self,
                    actualType = type(obj), typeMustBe = self.__objType)
            checkResult = self.__check(*argsToCheck)
        if checkResult: addArc((subj, self, obj))
    def prepareArgs(self, **kwargs):
        self._checkRoleNames(kwargs.keys())
        cnt = 0
        for argn in self.roleNames:
            cnt += 1
            yield kwargs.get(argn, ANY)
        else:
            if cnt < 2: yield UNIT
            if cnt < 1: yield UNIT
    def decodeQuadruples(self, quadruples):
        for _, s, p, o in quadruples:
            yield {'predicate': p,
                   self._roleNames[0]: s,
                   self._roleNames[1]: o}
    def decodeQuadruple(self, t, s, p, o):
        return {'predicate': p,
                self._roleNames[0]: s,
                self._roleNames[1]: o}
